<script lang="tsx">
import {
  ElTable,
  ElTableColumn,
  ElPagination,
  ComponentSize,
  ElTooltipProps,
  ElImage,
  ElEmpty,
  ElCard,
  ElRadio
} from 'element-plus'
import { defineComponent, PropType, ref, computed, unref, watch, onMounted, nextTick } from 'vue'
import { propTypes } from '@/utils/propTypes'
import { setIndex } from './helper'
import type { TableProps, TableColumn, Pagination, TableSetProps } from './types'
import { set, get } from 'lodash-es'
import { CSSProperties } from 'vue'
import { getSlot } from '@/utils/tsxHelper'
import TableActions from './components/TableActions.vue'
import { createVideoViewer } from '@/components/VideoPlayer'
import { BaseButton } from '@/components/Button'
import { Icon } from '@/components/Icon'

export default defineComponent({
  name: 'Table',
  props: {
    isSelect: propTypes.bool.def(false),
    isMultiple: propTypes.bool.def(false),
    checkIsSelect: propTypes.func.def(() => {
      return true
    }),
    selectKey: propTypes.array,
    isShowNo: propTypes.bool.def(true),
    // 是否展示表格的工具栏
    showAction: propTypes.bool.def(true),
    // 是否所有的超出隐藏，优先级低于schema中的showOverflowTooltip,
    showOverflowTooltip: propTypes.bool.def(true),
    // 表头
    columns: {
      type: Array as PropType<TableColumn[]>,
      default: () => []
    },
    // 是否展示分页
    pagination: {
      type: Object as PropType<Pagination>,
      default: (): Pagination | undefined => undefined
    },
    // 仅对 type=selection 的列有效，类型为 Boolean，为 true 则会在数据更新之后保留之前选中的数据（需指定 row-key）
    reserveSelection: propTypes.bool.def(false),
    // 加载状态
    loading: propTypes.bool.def(false),
    // 是否叠加索引
    reserveIndex: propTypes.bool.def(true),
    // 对齐方式
    align: propTypes.string
      .validate((v: string) => ['left', 'center', 'right'].includes(v))
      .def('left'),
    // 表头对齐方式
    headerAlign: propTypes.string
      .validate((v: string) => ['left', 'center', 'right'].includes(v))
      .def('center'),
    data: {
      type: Array as PropType<Recordable[]>,
      default: () => []
    },
    // 图片自动预览字段数组
    imagePreview: {
      type: Array as PropType<string[]>,
      default: () => []
    },
    // 视频自动预览字段数组
    videoPreview: {
      type: Array as PropType<string[]>,
      default: () => []
    },
    height: propTypes.oneOfType([Number, String]),
    maxHeight: propTypes.oneOfType([Number, String]),
    stripe: propTypes.bool.def(false),
    border: propTypes.bool.def(true),
    size: {
      type: String as PropType<ComponentSize>,
      validator: (v: ComponentSize) => ['medium', 'small', 'mini'].includes(v)
    },
    fit: propTypes.bool.def(true),
    showHeader: propTypes.bool.def(true),
    highlightCurrentRow: propTypes.bool.def(false),
    currentRowKey: propTypes.oneOfType([Number, String]),
    // row-class-name, 类型为 (row: Recordable, rowIndex: number) => string | string
    rowClassName: {
      type: [Function, String] as PropType<(row: Recordable, rowIndex: number) => string | string>,
      default: ''
    },
    rowStyle: {
      type: [Function, Object] as PropType<
        (row: Recordable, rowIndex: number) => Recordable | CSSProperties
      >,
      default: () => undefined
    },
    cellClassName: {
      type: [Function, String] as PropType<
        (row: Recordable, column: any, rowIndex: number) => string | string
      >,
      default: ''
    },
    cellStyle: {
      type: [Function, Object] as PropType<
        (row: Recordable, column: any, rowIndex: number) => Recordable | CSSProperties
      >,
      default: () => undefined
    },
    headerRowClassName: {
      type: [Function, String] as PropType<(row: Recordable, rowIndex: number) => string | string>,
      default: ''
    },
    headerRowStyle: {
      type: [Function, Object] as PropType<
        (row: Recordable, rowIndex: number) => Recordable | CSSProperties
      >,
      default: () => undefined
    },
    headerCellClassName: {
      type: [Function, String] as PropType<
        (row: Recordable, column: any, rowIndex: number) => string | string
      >,
      default: ''
    },
    headerCellStyle: {
      type: [Function, Object] as PropType<
        (row: Recordable, column: any, rowIndex: number) => Recordable | CSSProperties
      >,
      default: () => undefined
    },
    rowKey: propTypes.string.def('id'),
    emptyText: propTypes.string.def('暂无数据'),
    defaultExpandAll: propTypes.bool.def(true),
    expandRowKeys: {
      type: Array as PropType<string[]>,
      default: () => []
    },
    defaultSort: {
      type: Object as PropType<{ prop: string; isDesc: boolean }>,
      default: () => ({})
    },
    tooltipEffect: {
      type: String as PropType<'dark' | 'light'>,
      default: 'dark'
    },
    tooltipOptions: {
      type: Object as PropType<
        Pick<
          ElTooltipProps,
          | 'effect'
          | 'enterable'
          | 'hideAfter'
          | 'offset'
          | 'placement'
          | 'popperClass'
          | 'popperOptions'
          | 'showAfter'
          | 'showArrow'
        >
      >,
      default: () => ({
        enterable: true,
        placement: 'top',
        showArrow: true,
        hideAfter: 200,
        popperOptions: { strategy: 'fixed' }
      })
    },
    showSummary: propTypes.bool.def(false),
    sumText: propTypes.string.def('Sum'),
    summaryMethod: {
      type: Function as PropType<(param: { columns: any[]; data: any[] }) => any[]>,
      default: () => undefined
    },
    spanMethod: {
      type: Function as PropType<
        (param: { row: any; column: any; rowIndex: number; columnIndex: number }) => any[]
      >,
      default: () => undefined
    },
    selectOnIndeterminate: propTypes.bool.def(true),
    indent: propTypes.number.def(16),
    lazy: propTypes.bool.def(false),
    load: {
      type: Function as PropType<(row: Recordable, treeNode: any, resolve: Function) => void>,
      default: () => undefined
    },
    treeProps: {
      type: Object as PropType<{ hasChildren?: string; children?: string; label?: string }>,
      default: () => ({ hasChildren: 'HasChildren', children: 'Children', label: 'Label' })
    },
    tableLayout: {
      type: String as PropType<'auto' | 'fixed'>,
      default: 'fixed'
    },
    scrollbarAlwaysOn: propTypes.bool.def(false),
    flexible: propTypes.bool.def(false),
    // 自定义内容
    customContent: propTypes.bool.def(false),
    cardBodyStyle: {
      type: Object as PropType<CSSProperties>,
      default: () => ({})
    },
    cardBodyClass: {
      type: String as PropType<string>,
      default: ''
    },
    cardWrapStyle: {
      type: Object as PropType<CSSProperties>,
      default: () => ({})
    },
    cardWrapClass: {
      type: String as PropType<string>,
      default: ''
    }
  },
  emits: [
    'register',
    'refresh',
    'change',
    'update:selectKey',
    'selected',
    'update:loading',
    'update:columns'
  ],
  setup(props, { attrs, emit, slots, expose }) {
    const elTableRef = ref<ComponentRef<typeof ElTable>>()
    // 注册
    onMounted(() => {
      const tableRef = unref(elTableRef)
      emit('register', tableRef?.$parent, elTableRef.value)
    })
    const isLoad = ref<boolean>(props.loading)
    let isLock = false
    const allKey: any[] = []
    const pageSizeRef = ref<number>(10)
    const currentPageRef = ref<number>(1)
    if (props.pagination != null) {
      if (props.pagination.pageSize == null) {
        pageSizeRef.value = props.pagination.defaultPageSize ?? 10
      } else {
        pageSizeRef.value = props.pagination.pageSize
      }
      if (props.pagination.currentPage == null) {
        currentPageRef.value = props.pagination.defaultCurrentPage ?? 1
      } else {
        currentPageRef.value = props.pagination.currentPage
      }
    }
    const sortBy = ref({
      prop: props.defaultSort.prop,
      order: props.defaultSort.isDesc ? 'descending' : 'ascending'
    })

    // useTable传入的props
    const outsideProps = ref<TableProps>({})
    const setProps = (props: TableProps = {}) => {
      mergeProps.value = Object.assign(unref(mergeProps), props)
      outsideProps.value = { ...props } as any
    }
    const selectable = (row) => {
      if (props.checkIsSelect == null) {
        return true
      }
      return props.checkIsSelect(row)
    }
    const mergeProps = ref<TableProps>({})
    const list: TableColumn[] = []
    if (props.isShowNo) {
      list.push({
        field: 'index',
        label: '序号',
        type: 'index',
        align: 'center',
        fixed: 'left',
        resizable: false,
        width: '80px'
      })
    }
    if (props.isSelect && props.isMultiple) {
      list.push({
        field: props.rowKey,
        type: 'selection',
        resizable: false,
        fixed: 'left',
        align: 'center',
        selectable: selectable,
        width: '60px'
      })
    } else if (props.isSelect && !props.isMultiple) {
      list.push({
        field: 'radio',
        resizable: false,
        align: 'center',
        fixed: 'left',
        width: '50px',
        slots: {
          default: (e) => {
            return (
              <ElRadio
                v-model={chioseKey.value}
                name={props.rowKey}
                disabled={selectable(e.row) == false}
                label={e.row[props.rowKey]}
              >
                {''}
              </ElRadio>
            )
          }
        }
      })
    }
    let cols = props.columns
    if (list.length > 0) {
      cols = list.concat(props.columns)
      setProps({ columns: cols })
    }
    const getProps = computed(() => {
      const propsObj = { ...props }
      propsObj.columns = cols
      Object.assign(propsObj, unref(mergeProps))
      return propsObj
    })
    const setColumn = (columnProps: TableSetProps[], columnsChildren?: TableColumn[]) => {
      const { columns } = unref(getProps)
      for (const v of columnsChildren || columns) {
        for (const item of columnProps) {
          if (v.field === item.field) {
            set(v, item.path, item.value)
          } else if (v.children?.length) {
            setColumn(columnProps, v.children)
          }
        }
      }
    }

    const addColumn = (column: TableColumn, index?: number) => {
      const { columns } = unref(getProps)
      if (index !== void 0) {
        columns.splice(index, 0, column)
      } else {
        columns.push(column)
      }
    }

    const delColumn = (field: string) => {
      const { columns } = unref(getProps)
      const index = columns.findIndex((item) => item.field === field)
      if (index > -1) {
        columns.splice(index, 1)
      }
    }

    const refresh = () => {
      loadData()
      emit('refresh')
    }

    const changSize = (size: ComponentSize) => {
      setProps({ size })
    }

    const confirmSetColumn = (columns: TableColumn[]) => {
      setProps({ columns })
    }

    expose({
      setProps,
      setColumn,
      delColumn,
      addColumn,
      elTableRef
    })
    watch(
      () => props.loading,
      (val) => {
        isLoad.value = val
      },
      {
        immediate: false
      }
    )
    watch(
      () => unref(isLoad),
      (val) => {
        emit('update:loading', val)
      },
      {
        immediate: false
      }
    )
    const pagination = computed(() => {
      const data = Object.assign(
        {
          small: false,
          background: false,
          layout: 'sizes, prev, pager, next, jumper, ->, total',
          pageSizes: [10, 20, 30, 40, 50, 100],
          defaultCurrentPage: 1,
          disabled: false,
          hideOnSinglePage: false
        },
        unref(getProps).pagination
      )
      delete data.pageSize
      delete data.currentPage
      return data
    })
    if (props.pagination != null) {
      watch(
        () => props.pagination.pageSize,
        (val) => {
          if (val != null) {
            pageSizeRef.value = val
          } else {
            pageSizeRef.value = props.pagination.defaultPageSize ?? 10
          }
        },
        {
          immediate: false
        }
      )
      watch(
        () => currentPageRef.value,
        (val: number) => {
          isLock = true
          loadData()
        }
      )
      watch(
        () => pageSizeRef.value,
        (val: number) => {
          if (unref(currentPageRef) == 1) {
            isLock = true
            loadData()
          } else {
            currentPageRef.value = 1
          }
        }
      )
    }
    const sortChange = function (e) {
      sortBy.value.prop = e.column.sortBy == null ? e.prop : e.column.sortBy
      sortBy.value.order = e.order
      loadData()
    }
    const getBindValue = computed(() => {
      const bindValue: Recordable = { ...attrs, ...unref(getProps) }
      delete bindValue.columns
      delete bindValue.data
      delete bindValue.align
      return bindValue
    })
    const chioseKey = ref<string | number | null>(null)
    if (props.selectKey && props.selectKey.length > 0) {
      if (props.isSelect && props.isMultiple == false) {
        chioseKey.value = props.selectKey[0] as string | number
      } else if (props.isSelect && props.isMultiple) {
        allKey.push(...props.selectKey)
      }
    }
    const selected = (keys, isRefresh) => {
      emit('update:selectKey', keys)
      if (isRefresh) {
        emit('selected', keys)
      }
    }
    const selectedEvent = (keys) => {
      if (isLoad.value || isLock) {
        return
      }
      isLoad.value = true
      const tKeys: any[] = []
      keys.forEach((c) => {
        const key = c[props.rowKey]
        tKeys.push(key)
        if (!allKey.includes(key)) {
          allKey.push(key)
        }
      })
      props.data.forEach((c) => {
        const key = c[props.rowKey]
        if (!tKeys.includes(key)) {
          const index = allKey.findIndex((a) => a == key)
          if (index != -1) {
            allKey.splice(index, 1)
          }
        }
      })
      isLoad.value = false
      selected(allKey, true)
    }
    if (props.isSelect && props.isMultiple == false) {
      watch(
        () => chioseKey.value,
        (val) => {
          selected(val == null ? [] : [val], false)
        },
        {
          immediate: false
        }
      )
      watch(
        () => props.selectKey,
        (val: string[]) => {
          const t = val == null || val.length == 0 ? null : val[0]
          if (t != chioseKey.value) {
            chioseKey.value = t
          }
        },
        {
          immediate: false
        }
      )
    } else if (props.isSelect && props.isMultiple) {
      watch(
        () => props.selectKey,
        (val: string[]) => {
          const ta = unref(elTableRef)
          if (ta) {
            if (val != null && val.length != 0) {
              props.data.forEach((c) => {
                if (val.includes(c[props.rowKey])) {
                  ta.toggleRowSelection(c, true)
                } else {
                  ta.toggleRowSelection(c, false)
                }
              })
            } else {
              ta.clearSelection()
            }
          }
        },
        {
          immediate: false,
          deep: true
        }
      )
    }
    const initAllKey = (val) => {
      if (val != null && val.length > 0 && allKey.length > 0) {
        nextTick(() => {
          val.forEach((c) => {
            const key = c[props.rowKey]
            if (allKey.includes(key)) {
              unref(elTableRef)?.toggleRowSelection(c, true)
            }
          })
          isLock = false
        })
      } else {
        isLock = false
      }
    }
    watch(
      () => props.data,
      (val) => {
        isLoad.value = true
        if (props.isSelect && props.isMultiple) {
          initAllKey(val)
        }
        nextTick(() => {
          isLoad.value = false
        })
      },
      {
        immediate: false
      }
    )
    const renderTreeTableColumn = (columnsChildren: TableColumn[]) => {
      const { align, headerAlign, showOverflowTooltip, imagePreview, videoPreview } =
        unref(getProps)
      return columnsChildren.map((v) => {
        if (v.hidden) return null
        const props = { ...v } as any
        if (props.children) delete props.children

        const children = v.children

        const curSlot = {
          default: (...args: any[]) => {
            const data = args[0]
            if (v.slotName != null && data.$index != -1) {
              return getSlot(slots, v.slotName, data)
            }
            let isPreview = false
            isPreview =
              imagePreview.some((item) => (item as string) === v.field) ||
              videoPreview.some((item) => (item as string) === v.field)

            return children && children.length
              ? renderTreeTableColumn(children)
              : props?.slots?.default
                ? props.slots.default(...args)
                : v?.formatter
                  ? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index)
                  : isPreview
                    ? renderPreview(get(data.row, v.field), v.field)
                    : get(data.row, v.field)
          }
        }
        if (props?.slots?.header) {
          curSlot['header'] = (...args: any[]) => props.slots.header(...args)
        }

        return (
          <ElTableColumn
            showOverflowTooltip={showOverflowTooltip}
            align={align}
            headerAlign={headerAlign}
            {...props}
            prop={v.field}
          >
            {curSlot}
          </ElTableColumn>
        )
      })
    }

    const renderPreview = (url: string, field: string) => {
      const { imagePreview, videoPreview } = unref(getProps)
      return (
        <div class="flex items-center">
          {imagePreview.includes(field) ? (
            <ElImage
              src={url}
              fit="cover"
              class="w-[100%]"
              lazy
              preview-src-list={[url]}
              preview-teleported
            />
          ) : videoPreview.includes(field) ? (
            <BaseButton
              type="primary"
              icon={<Icon icon="ep:video-play" />}
              onClick={() => {
                createVideoViewer({
                  url
                })
              }}
            >
              预览
            </BaseButton>
          ) : null}
        </div>
      )
    }
    const renderTableColumn = (columnsChildren?: TableColumn[]) => {
      const {
        columns,
        reserveIndex,
        align,
        headerAlign,
        showOverflowTooltip,
        reserveSelection,
        imagePreview,
        videoPreview
      } = unref(getProps)

      return (columnsChildren || columns).map((v) => {
        if (v.hidden) return null
        if (v.type === 'index') {
          return (
            <ElTableColumn
              type="index"
              index={
                v.index
                  ? v.index
                  : (index) =>
                      setIndex(reserveIndex, index, unref(pageSizeRef), unref(currentPageRef))
              }
              align={v.align || align}
              headerAlign={v.headerAlign || headerAlign}
              label={v.label}
              fixed={v.fixed}
              width="65px"
            ></ElTableColumn>
          )
        } else if (v.type === 'selection') {
          return (
            <ElTableColumn
              type="selection"
              reserveSelection={reserveSelection}
              align={align}
              headerAlign={headerAlign}
              selectable={v.selectable}
              width="50"
            ></ElTableColumn>
          )
        } else {
          const props = { ...v } as any
          if (props.children) delete props.children

          const children = v.children

          const curSlot = {
            default: (...args: any[]) => {
              const data = args[0]
              if (v.slotName != null && data.$index != -1) {
                return getSlot(slots, v.slotName, data)
              }
              let isPreview = false
              isPreview =
                imagePreview.some((item) => (item as string) === v.field) ||
                videoPreview.some((item) => (item as string) === v.field)

              return children && children.length
                ? renderTreeTableColumn(children)
                : props?.slots?.default
                  ? props.slots.default(...args)
                  : v?.formatter
                    ? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index)
                    : isPreview
                      ? renderPreview(get(data.row, v.field), v.field)
                      : get(data.row, v.field)
            }
          }
          if (props?.slots?.header) {
            curSlot['header'] = (...args: any[]) => props.slots.header(...args)
          }
          return (
            <ElTableColumn
              showOverflowTooltip={showOverflowTooltip}
              align={align}
              headerAlign={headerAlign}
              {...props}
              prop={v.field}
            >
              {curSlot}
            </ElTableColumn>
          )
        }
      })
    }

    const loadData = () => {
      if (isLoad.value) {
        return
      }
      isLoad.value = true
      emit('change', {
        Size: pageSizeRef.value,
        Index: currentPageRef.value,
        SortName: sortBy.value.prop,
        IsDesc: sortBy.value.order == 'descending'
      })
    }
    return () => {
      const tableSlots = {}
      if (getSlot(slots, 'empty')) {
        tableSlots['empty'] = (...args: any[]) => getSlot(slots, 'empty', args)
      }
      if (getSlot(slots, 'append')) {
        tableSlots['append'] = (...args: any[]) => getSlot(slots, 'append', args)
      }
      const isShow =
        (unref(getProps).showAction && !unref(getProps).customContent) || slots['title'] != null
      const titleStype = {
        display: isShow ? 'block' : 'none',
        width: '100%',
        lineHeight: '35px',
        marginBottom: '5px'
      }
      return (
        <div v-loading={unref(isLoad)}>
          {unref(getProps).customContent ? (
            <div class="flex flex-wrap">
              {unref(getProps)?.data?.length ? (
                unref(getProps)?.data.map((item) => {
                  const cardSlots = {
                    default: () => {
                      return getSlot(slots, 'content', item)
                    }
                  }
                  if (getSlot(slots, 'content-header')) {
                    cardSlots['header'] = () => {
                      return getSlot(slots, 'content-header', item)
                    }
                  }
                  if (getSlot(slots, 'content-footer')) {
                    cardSlots['footer'] = () => {
                      return getSlot(slots, 'content-footer', item)
                    }
                  }
                  return (
                    <ElCard
                      shadow="hover"
                      class={unref(getProps).cardWrapClass}
                      style={unref(getProps).cardWrapStyle}
                      bodyClass={unref(getProps).cardBodyClass}
                      bodyStyle={unref(getProps).cardBodyStyle}
                    >
                      {cardSlots}
                    </ElCard>
                  )
                })
              ) : (
                <div class="flex flex-1 justify-center">
                  <ElEmpty description="暂无数据" />
                </div>
              )}
            </div>
          ) : (
            <>
              <div style={titleStype} class="h-35px">
                {unref(getProps).showAction && !unref(getProps).customContent ? (
                  <TableActions
                    columns={unref(getProps).columns}
                    onChangSize={changSize}
                    onRefresh={refresh}
                    onConfirm={confirmSetColumn}
                  />
                ) : null}
                {getSlot(slots, 'title') ? (
                  <div class="text-left" style="float:left">
                    {{ default: () => getSlot(slots, 'title') }}
                  </div>
                ) : null}
              </div>
              <ElTable
                ref={elTableRef}
                data={unref(getProps).data}
                onSort-change={sortChange}
                current-row-key={unref(chioseKey)}
                highlightCurrentRow={props.isSelect && props.isMultiple == false}
                onSelection-change={selectedEvent}
                onCurrent-change={(val) => {
                  if (props.isSelect && props.isMultiple == false && props.rowKey) {
                    chioseKey.value = val[props.rowKey]
                  }
                }}
                {...unref(getBindValue)}
              >
                {{
                  default: () => renderTableColumn(),
                  ...tableSlots
                }}
              </ElTable>
            </>
          )}
          {unref(getProps).pagination ? (
            <ElPagination
              v-model:pageSize={pageSizeRef.value}
              v-model:currentPage={currentPageRef.value}
              class="mt-10px"
              {...unref(pagination)}
            ></ElPagination>
          ) : undefined}
        </div>
      )
    }
  }
})
</script>
